﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace InstantSpine.Core.ParaSpine
{
    /// <summary>
    /// Parameter associated to a simple scalar parameter used in ParaSpine
    /// </summary>
    /// <remarks>Compound parameters (e.g. material properties) require
    /// more sophisticated handling than this class provides</remarks>
    /// <typeparam name="T">Type of the parameter value</typeparam>
    [DataContract]
    public sealed class ParaSpineParameter<T> : INotifyPropertyChanged
       where T : struct, IEquatable<T>
    {
        public event PropertyChangedEventHandler PropertyChanged;
        
        Func<T, bool> canChangeValuePredicate;
        public Func<T, bool> CanChangeValue {
            set { canChangeValuePredicate = value; }
            get {
                if (canChangeValuePredicate == null)
                    canChangeValuePredicate = _ => true;
                return canChangeValuePredicate;
            }
        }

        [DataMember]
        string paraSpineParamName;
        /// <summary>
        /// Name of the corresponding ParaSpine parameter
        /// </summary>
        public string ParaSpineParameterName {
            get { return paraSpineParamName; }
        }
        
        T paramValue;
        /// <summary>
        /// Value of the parameter
        /// </summary>
        [DataMember]
        public T ParameterValue {
            get { return paramValue; }
            set {
                if (!paramValue.Equals(value) &&
                    CanChangeValue(value)) {
                    paramValue = value;
                    PropertyChanged.Raise(this);
                    //Debug.WriteLine("changed {0} <- {1}", paraSpineParamName, paramValue);
                }
            }
        }

        public ParaSpineParameter(string paraSpineParameterName, T value, Func<T, bool> canChange = null) {
            this.paramValue = value;
            this.paraSpineParamName = paraSpineParameterName;
            this.canChangeValuePredicate = canChange ?? (_ => true);
        }

        /// <summary>
        /// Get a string of APDL code defining the corresponding parameter
        /// </summary>
        /// <returns>A string of the form "param_name = value"</returns>
        public string ToApdlSource() {            
            return string.Format("{0} = {1}", paraSpineParamName, paramValue);
        }

        public static implicit operator T(ParaSpineParameter<T> psp)
        {
            return psp.ParameterValue;
        }
    }
}
